﻿#pragma once

#include "core/log/LogSystem.h"

namespace Engine
{
	class BufferLayout;

	class ENGINE_API AbstractBuffer
	{
	public:
		virtual ~AbstractBuffer() = default;

		virtual void Bind() const = 0;
		virtual void UnBind() const = 0;
	};

	class ENGINE_API VertexBuffer : public AbstractBuffer
	{
	public:
		[[nodiscard]] virtual const BufferLayout& GetLayout() const = 0;
		virtual void SetLayout(const BufferLayout& layout) = 0;

		static VertexBuffer* Create(const float* vertices, uint32_t size);
	};

	class ENGINE_API IndexBuffer : public AbstractBuffer
	{
	public:
		static IndexBuffer* Create(const unsigned int* indices, uint32_t count);

		[[nodiscard]] virtual uint32_t GetCount() const = 0;
	};

	class ENGINE_API VertexArray : public AbstractBuffer
	{
	public:
		virtual void AddVertexBuffer(const Ref<VertexBuffer>& vertexBuffer) = 0;
		virtual void SetIndexBuffer(const Ref<IndexBuffer>& indexBuffer) = 0;

		[[nodiscard]] virtual const std::vector<Ref<VertexBuffer>>& GetVertexBuffers() const = 0;
		[[nodiscard]] virtual const Ref<IndexBuffer>& GetIndexBuffer() const = 0;

		static VertexArray* Create();
	};


	// ---------------------------------------------------------------------------------------------------------------------------


	enum class ShaderDateType : uint8_t
	{
		None = 0,
		Float, Float2, Float3, Float4, Mat3, Mat4, Int, Int2, Int3, Int4, Bool
	};

	static uint32_t ShaderDateTypeSize(const ShaderDateType type) // NOLINT(clang-diagnostic-unused-function)
	{
		switch (type) // NOLINT(clang-diagnostic-switch-enum)
		{
			case ShaderDateType::Float:	 return 4;
			case ShaderDateType::Float2: return 4 * 2;
			case ShaderDateType::Float3: return 4 * 3;
			case ShaderDateType::Float4: return 4 * 4;
			case ShaderDateType::Mat3:   return 4 * 3 * 3;
			case ShaderDateType::Mat4:   return 4 * 4 * 4;
			case ShaderDateType::Int:    return 4;
			case ShaderDateType::Int2:	 return 4 * 2;
			case ShaderDateType::Int3:	 return 4 * 3;
			case ShaderDateType::Int4:	 return 4 * 4;
			case ShaderDateType::Bool:	 return 1;
			default:
			{
				ENGINE_ASSERT(false, "未知 Shader Data 类型！")
				return 0;
			}
		}
	}

	struct BufferElement
	{
		std::string name;
		ShaderDateType type;
		uint32_t size;
		uint32_t offset;
		bool normalized;

		BufferElement() = default;

		BufferElement(const ShaderDateType type, const std::string& name, bool normalized = false)
			: name(name), type(type), size(ShaderDateTypeSize(type)), offset(0), normalized(false)
		{
		}

		[[nodiscard]] uint32_t GetElementCount() const
		{
			switch (type)  // NOLINT(clang-diagnostic-switch-enum)
			{
				case ShaderDateType::Float:	 return 1;
				case ShaderDateType::Float2: return 2;
				case ShaderDateType::Float3: return 3;
				case ShaderDateType::Float4: return 4;
				case ShaderDateType::Mat3:   return 3;
				case ShaderDateType::Mat4:   return 4;
				case ShaderDateType::Int:    return 1;
				case ShaderDateType::Int2:	 return 2;
				case ShaderDateType::Int3:	 return 3;
				case ShaderDateType::Int4:	 return 4;
				case ShaderDateType::Bool:	 return 1;
				default:
				{
					ENGINE_ASSERT(false, "未知 Shader Data 类型！")
					return 0;
				}
			}
		}
	};

	class BufferLayout
	{
	public:
		BufferLayout() = default;

		BufferLayout(const std::initializer_list<BufferElement>& elements)
			: elements(elements)
		{
			CalculateOffsetAndStride();
		}

		[[nodiscard]] std::vector<BufferElement>::const_iterator begin() const { return elements.begin(); }
		[[nodiscard]] std::vector<BufferElement>::const_iterator end() const { return elements.end(); }

		[[nodiscard]] inline const std::vector<BufferElement>& GetElement() const { return elements; }
		[[nodiscard]] inline uint32_t GetStride() const{ return stride; }

	private:
		std::vector<BufferElement> elements;
		uint32_t stride = 0;

		inline void CalculateOffsetAndStride()
		{
			uint32_t offset = 0;
			stride = 0;
			for (auto& element : elements)
			{
				element.offset = offset;
				offset += element.size;
				stride += element.size;
			}
		}
	};
}
